// OpenGL Shader Material
// (c) jimon game studio

#include "CShaderMaterial_OpenGL.h"
#include "ILogManager.h"

#include "IVariables.h"

#ifdef JE_RENDER_OPENGL

#include "CRender_OpenGL.h"

namespace je
{
	namespace video
	{
		#define JE_GET_CTX Pointers[CurrentRender->GetShadersGlobalTechnique()]

		//! Constructor
		CShaderMaterial_OpenGL::CShaderMaterial_OpenGL(IRender * Render,CRender_OpenGL_Extensions * NewExtensions,const jeStringc & Source,ShaderDefinesListType * DefinesList,OpenGLWorkMode WorkMode)
			:CurrentRender(Render),Extensions(NewExtensions),CurrentWorkMode(WorkMode),CurrentlyBinded(false),IgnoreLocationErrorForNewVariables(false)
		{
			OriginalShaderSource = Source;

			if(CurrentWorkMode != OWM_11)
				CompileShaderProgram(Source,DefinesList);
		}

		//! Destructor
		CShaderMaterial_OpenGL::~CShaderMaterial_OpenGL()
		{
			ClearSetupUniformList();

			if(CurrentWorkMode != OWM_11)
			{
				static_cast<CRender_OpenGL*>(CurrentRender)->RemoveShaderMaterial(this);
				RemoveShaderProgram();
			}
		}

		//! Get Variable
		IShaderVariable * CShaderMaterial_OpenGL::GetVariable(const jeStringc & Name,u1 CreateNewInstance)
		{
			if(!CreateNewInstance)
				for(ShaderVariablesListType::Iterator Iter = ShaderVariablesList.begin();Iter != ShaderVariablesList.end();Iter++)
					if((*Iter)->GetName() == Name)
						return *Iter;

			CShaderVariable_OpenGL * Variable = NULL;
			JENEW(Variable,CShaderVariable_OpenGL(CurrentRender,this,Extensions,Name,SPForShaderVariables,CurrentWorkMode,IgnoreLocationErrorForNewVariables))
			ShaderVariablesList.push_back(Variable);
			return Variable;
		}

		//! Bind
		void CShaderMaterial_OpenGL::Bind()
		{
			CurrentlyBinded = true;
			if(CurrentWorkMode != OWM_11)
			{
				Extensions->UseProgram(JE_GET_CTX.SP);
				for(u32 i=0;i<ShaderSetupUniformList.GetSize();i++)
				{
					ShaderSetupUniform * SetupUniform = ShaderSetupUniformList[i];

					if(SetupUniform->SP == JE_GET_CTX.SP)
					{
						if(SetupUniform->Location != -1)
						{
							switch(SetupUniform->DataType)
							{
							case SSUDT_MATRIX_44F:
								{
									f32 * Data = reinterpret_cast<f32*>(SetupUniform->Data);
									Extensions->SetUniformMatrix4fv(SetupUniform->Location,1,true,Data);
									break;
								}
							case SSUDT_MATRIX_33F:
								{
									// TODO
									f32 * Data = reinterpret_cast<f32*>(SetupUniform->Data);
									Extensions->SetUniformMatrix3fv(SetupUniform->Location,1,true,Data);
									break;
								}
							case SSUDT_ARRAY_S32:
								{
									s32 * Data = reinterpret_cast<s32*>(SetupUniform->Data);
									Extensions->SetUniform1iv(SetupUniform->Location,SetupUniform->DataSize / sizeof(s32),Data);
									break;
								}
							case SSUDT_ARRAY_F32:
								{
									f32 * Data = reinterpret_cast<f32*>(SetupUniform->Data);
									Extensions->SetUniform1fv(SetupUniform->Location,SetupUniform->DataSize / sizeof(f32),Data);
									break;
								}
							case SSUDT_ARRAY_VECTOR_3DF:
								{
									GLfloat * Data = reinterpret_cast<GLfloat*>(SetupUniform->Data);
									Extensions->SetUniform3fv(SetupUniform->Location,SetupUniform->DataSize / sizeof(jeVector3df),Data);
									break;
								}
							case SSUDT_VECTOR_3DF:
								{
									jeVector3df * Data = reinterpret_cast<jeVector3df*>(SetupUniform->Data);
									Extensions->SetUniform3fp(SetupUniform->Location,Data->X,Data->Y,Data->Z);
									break;
								}
							case SSUDT_VECTOR_3DI:
								{
									jeVector3di * Data = reinterpret_cast<jeVector3di*>(SetupUniform->Data);
									Extensions->SetUniform3ip(SetupUniform->Location,Data->X,Data->Y,Data->Z);
									break;
								}
							case SSUDT_VECTOR_2DF:
								{
									jeVector2df * Data = reinterpret_cast<jeVector2df*>(SetupUniform->Data);
									Extensions->SetUniform2fp(SetupUniform->Location,Data->X,Data->Y);
									break;
								}
							case SSUDT_VECTOR_2DI:
								{
									jeVector2di * Data = reinterpret_cast<jeVector2di*>(SetupUniform->Data);
									Extensions->SetUniform2ip(SetupUniform->Location,Data->X,Data->Y);
									break;
								}
							default:
								JELOG_E("Unknown Setup Uniform Data Type")
								break;
							};
						}

						JEDELETE_A(SetupUniform->Data)
						JEDELETE(SetupUniform)

						ShaderSetupUniformList.EraseElement(i--); // TODO optimize this !
					}
					else
					{
						u1 SPExist = false;
						for(u32 pi=0;pi<JE_MAX_SHADER_GLOBAL_TECHNIQUES;i++)
							if(SetupUniform->SP == Pointers[pi].SP)
							{
								SPExist = true;
								break;
							}

						if(!SPExist)
						{
							JEDELETE_A(SetupUniform->Data)
							JEDELETE(SetupUniform)
							ShaderSetupUniformList.EraseElement(i--);
						}
					}
				}
			}
		}

		//! UnBind
		void CShaderMaterial_OpenGL::UnBind()
		{
			CurrentlyBinded = false;
			if((CurrentWorkMode != OWM_11) && (CurrentWorkMode != OWM_30))
				Extensions->UseProgram(NULL);
		}

		//! Set Vertex Attribute
		void CShaderMaterial_OpenGL::SetVertexAttribute(HardwareBufferArrayLayers Layer,u1 Enable,s8 VariablesPerObject,GLenum VariableType,u1 Normalize,s32 Stride,const void * Pointer)
		{
			if(JE_GET_CTX.AttributeLocations[Layer] == -1)
				return;

			if(Enable)
			{
				Extensions->EnableVertexAttributeArray(JE_GET_CTX.AttributeLocations[Layer]);
				Extensions->SetVertexAttributePointer(JE_GET_CTX.AttributeLocations[Layer],VariablesPerObject,VariableType,Normalize,Stride,Pointer);
			}
			else
				Extensions->DisableVertexAttributeArray(JE_GET_CTX.AttributeLocations[Layer]);
		}

		//! Set Matrixes
		void CShaderMaterial_OpenGL::SetMatrixes(const jeMatrix4f & Matrix,ShaderMaterialInternalMatrixType Type)
		{
			switch(Type)
			{
			case SMIMT_MODELVIEWPROJECTION:
				{
					if(JE_GET_CTX.ModelViewProjectionMatrix != -1)
						Extensions->SetUniformMatrix4fv(JE_GET_CTX.ModelViewProjectionMatrix,1,true,Matrix.Element);
					break;
				}
			case SMIMT_MODELVIEW:
				{
					if(JE_GET_CTX.ModelViewMatrix != -1)
						Extensions->SetUniformMatrix4fv(JE_GET_CTX.ModelViewMatrix,1,true,Matrix.Element);
					break;
				}
			case SMIMT_MODEL:
				{
					if(JE_GET_CTX.ModelMatrix != -1)
						Extensions->SetUniformMatrix4fv(JE_GET_CTX.ModelMatrix,1,true,Matrix.Element);
					break;
				}
			case SMIMT_NORMAL:
				{
					if(JE_GET_CTX.NormalMatrix != -1)
						Extensions->SetUniformMatrix4fv(JE_GET_CTX.NormalMatrix,1,true,Matrix.Element);
					break;
				}
			case SMIMT_TEXTURE:
				{
					if(JE_GET_CTX.TextureMatrix != -1)
						Extensions->SetUniformMatrix4fv(JE_GET_CTX.TextureMatrix,1,true,Matrix.Element);
					break;
				}
			}
		}

		//! Need To Calculate Matrix
		u1 CShaderMaterial_OpenGL::NeedToCalculateMatrix(ShaderMaterialInternalMatrixType Type)
		{
			switch(Type)
			{
			case SMIMT_MODELVIEWPROJECTION:
				return (JE_GET_CTX.ModelViewProjectionMatrix!=-1);
			case SMIMT_MODELVIEW:
				return (JE_GET_CTX.ModelViewMatrix!=-1);
			case SMIMT_MODEL:
				return (JE_GET_CTX.ModelMatrix!=-1);
			case SMIMT_NORMAL:
				return (JE_GET_CTX.NormalMatrix!=-1);
			case SMIMT_TEXTURE:
				return (JE_GET_CTX.TextureMatrix!=-1);
			default:
				return false;
			}
		}

		//! Bind Texture Layer
		void CShaderMaterial_OpenGL::BindTextureLayer(GLuint Id,s32 LayerId)
		{
		}

		//! Recompile Shader Material
		void CShaderMaterial_OpenGL::Recompile(ShaderDefinesListType * DefinesList)
		{
			if(CurrentWorkMode == OWM_11)
				return;

			RemoveShaderProgram();
			CompileShaderProgram(OriginalShaderSource,DefinesList);
		}

		//! Remove Variable
		void CShaderMaterial_OpenGL::RemoveVariable(CShaderVariable_OpenGL * Variable)
		{
			for(ShaderVariablesListType::Iterator Iter = ShaderVariablesList.begin();Iter!=ShaderVariablesList.end();Iter++)
				if((*Iter) == Variable)
				{
					ShaderVariablesList.erase(Iter);
					return;
				}
		}

		//! Get Shader Setup Uniform List
		CShaderMaterial_OpenGL::ShaderSetupUniformListType & CShaderMaterial_OpenGL::GetShaderSetupUniformList()
		{
			return ShaderSetupUniformList;
		}

		//! Is Binded
		u1 CShaderMaterial_OpenGL::IsBinded()
		{
			return CurrentlyBinded;
		}

		//! Is Variable Registered
		u1 CShaderMaterial_OpenGL::IsVariableRegistered(const jeStringc & Name)
		{
			if(CurrentWorkMode == OWM_11)
				return false;

			for(ShaderVariablesListType::Iterator Iter = ShaderVariablesList.begin();Iter != ShaderVariablesList.end();Iter++)
				if((*Iter)->GetName() == Name)
					return true;
			return false;
		}

		//! Clear Uniform Setup List
		void CShaderMaterial_OpenGL::ClearSetupUniformList()
		{
			for(u32 i=0;i<ShaderSetupUniformList.GetSize();i++)
			{
				JEDELETE_A(ShaderSetupUniformList[i]->Data)
				JEDELETE(ShaderSetupUniformList[i])
			}
			ShaderSetupUniformList.Clear();
		}

		//! Set Ignore Location Error For New Variables
		void CShaderMaterial_OpenGL::SetIgnoreLocationErrorForNewVariables(u1 Enabled)
		{
			IgnoreLocationErrorForNewVariables = Enabled;
		}

		//! Compile Shader Program
		void CShaderMaterial_OpenGL::CompileShaderProgram(const jeStringc & Source,ShaderDefinesListType * DefinesList)
		{
			s32 GLSLVersion = core::GetVariables()->GetVariables32("OpenGL_Support_GLSL_Version");

			jeStringc ShaderVersion = jeStringc("#version ")+jeStringc(GLSLVersion)+jeStringc("\n");

			jeStringc BaseShaderDefines;
			if(GLSLVersion < 130)
			{
				BaseShaderDefines += "\
									#define OUTCOLOR0 gl_FragData[0]\n\
									#define OUTCOLOR1 gl_FragData[1]\n\
									#define OUTCOLOR2 gl_FragData[2]\n\
									#define OUTCOLOR3 gl_FragData[3]\n\
									#define OUTCOLOR4 gl_FragData[4]\n\
									#define OUTCOLOR5 gl_FragData[5]\n\
									#define OUTCOLOR6 gl_FragData[6]\n\
									#define OUTCOLOR7 gl_FragData[7]\n\
									#define USEOUTCOLOR(_A)\n\
									#define FIN attribute\n\
									#define FOUT varying\n\
									#define SIN varying\n\
									#define SOUT out\n\
									";
			}
			else
			{
				BaseShaderDefines += "\
									precision mediump int;\n\
									precision mediump float;\n\
									#define FIN in\n\
									#define FOUT out\n\
									#define SIN in\n\
									#define SOUT out\n\
									#define USEOUTCOLOR(_A) SOUT vec4 OUTCOLOR##_A;\n\
									#ifdef FS\n\
										SOUT vec4 OUTCOLOR0;\n\
									#endif\n\
									";
			}

			BaseShaderDefines += "vec4 vec2tovec4compress(vec2 value){return vec4(value.x,fract(value.x*256.0),value.y,fract(value.y*256.0));}\nvec2 vec4tovec2decompress(vec4 value){return vec2(value.x+value.y*1.0/256.0,value.z+value.w*1.0/256.0);}\n";

			for(u8 i=0;i < JE_MAX_SHADER_GLOBAL_TECHNIQUES;i++)
			{
				jeStringc ShaderDefines = BaseShaderDefines;

				u1 SetShaderDefines = false;

				for(ShaderDefinesListType::Iterator Iter = DefinesList->begin();Iter!=DefinesList->end();Iter++)
				{
					if((((*Iter)->Technique!=0) && ((*Iter)->Technique == i)) || ((*Iter)->Technique==0))
					{
						ShaderDefines += "#define ";
						ShaderDefines += (*Iter)->Name;
						ShaderDefines += " ";
						ShaderDefines += (*Iter)->Define;
						ShaderDefines += "\n";
						if((*Iter)->Technique!=0)
							SetShaderDefines = true;
					}
				}

				if((i!=0) && (!SetShaderDefines))
				{
					SPForShaderVariables[i] = 0;
					Pointers[i].SP = 0;
					Pointers[i].VS = 0;
					Pointers[i].FS = 0;
					continue;
				}

				jeStringc VSShader = ShaderVersion + jeStringc("#define VS\n") + ShaderDefines + Source;
				jeStringc FSShader = ShaderVersion + jeStringc("#define FS\n") + ShaderDefines + Source;

				Pointers[i].VS = Extensions->CreateShader(GL_VERTEX_SHADER);
				CompileShaderSource(Pointers[i].VS,VSShader.c_str());

				Pointers[i].FS = Extensions->CreateShader(GL_FRAGMENT_SHADER);
				CompileShaderSource(Pointers[i].FS,FSShader.c_str());

				Pointers[i].SP = Extensions->CreateProgram();
				Extensions->AttachShader(Pointers[i].SP,Pointers[i].VS);
				Extensions->AttachShader(Pointers[i].SP,Pointers[i].FS);

				SPForShaderVariables[i] = Pointers[i].SP;

				if(CurrentWorkMode >= OWM_31)
					for(s32 ci=0;ci<8;ci++)
						Extensions->BindFragmentDataLocation(Pointers[i].SP,ci,(jeStringc("OUTCOLOR")+jeStringc(ci)).c_str()); //probably we need setup this in other way

				Extensions->LinkProgram(Pointers[i].SP);
				//Extensions->ValidateProgram(Pointers[i].SP);

				s32 LinkedStatus;
				Extensions->GetProgramInfoState(Pointers[i].SP,GL_LINK_STATUS,&LinkedStatus);
				if(!LinkedStatus)
				{
					c8 LogInfo[MAX_LOG_SIZE];
					memset(LogInfo,0,MAX_LOG_SIZE);
					Extensions->GetProgramInfoLog(Pointers[i].SP,MAX_LOG_SIZE,NULL,LogInfo);
					JELOG_E(jeStringc("Cant link shader program : ")+jeStringc(LogInfo))
				}

				Pointers[i].AttributeLocations[HBAL_POSITION] = Extensions->GetAttributeLocation(Pointers[i].SP,"IPosition");
				Pointers[i].AttributeLocations[HBAL_UV] = Extensions->GetAttributeLocation(Pointers[i].SP,"ITexCoord");
				Pointers[i].AttributeLocations[HBAL_COLOR] = Extensions->GetAttributeLocation(Pointers[i].SP,"IColor");
				Pointers[i].AttributeLocations[HBAL_NORMAL] = Extensions->GetAttributeLocation(Pointers[i].SP,"INormal");
				Pointers[i].AttributeLocations[HBAL_TANGENT] = Extensions->GetAttributeLocation(Pointers[i].SP,"ITangent");
				Pointers[i].AttributeLocations[HBAL_BONES_IDS] = Extensions->GetAttributeLocation(Pointers[i].SP,"IBonesIds");
				Pointers[i].AttributeLocations[HBAL_BONES_W] = Extensions->GetAttributeLocation(Pointers[i].SP,"IBonesW");
				Pointers[i].AttributeLocations[HBAL_USER_1] = Extensions->GetAttributeLocation(Pointers[i].SP,"IUser1");
				Pointers[i].AttributeLocations[HBAL_USER_2] = Extensions->GetAttributeLocation(Pointers[i].SP,"IUser2");

				Pointers[i].ModelViewProjectionMatrix = Extensions->GetUniformLocation(Pointers[i].SP,"MVPMat");
				Pointers[i].ModelViewMatrix = Extensions->GetUniformLocation(Pointers[i].SP,"MVMat");
				Pointers[i].ModelMatrix = Extensions->GetUniformLocation(Pointers[i].SP,"MMat");
				Pointers[i].NormalMatrix = Extensions->GetUniformLocation(Pointers[i].SP,"NMat");
				Pointers[i].TextureMatrix = Extensions->GetUniformLocation(Pointers[i].SP,"TextureMatrix");

				Extensions->UseProgram(Pointers[i].SP);

				GLint TextureDefUniform = Extensions->GetUniformLocation(Pointers[i].SP,"Texture");
				if(TextureDefUniform != -1)
				{
					s32 TextureDefIndex = 0;
					Extensions->SetUniform1iv(TextureDefUniform,1,&TextureDefIndex);
				}
				for(s32 i2=0;i2<JE_MAX_MATERIAL_TEXTURES;i2++)
				{
					GLint TextureUniform = Extensions->GetUniformLocation(Pointers[i].SP,(jeStringc("Texture")+jeStringc(i2+1)).c_str());
					if(TextureUniform != -1)
						Extensions->SetUniform1iv(TextureUniform,1,&i2);
				}
				if(CurrentWorkMode != OWM_30)
					Extensions->UseProgram(NULL);

				for(ShaderVariablesListType::Iterator Iter = ShaderVariablesList.begin();Iter!=ShaderVariablesList.end();Iter++)
					(*Iter)->GetLocations();
			}
		}

		//! Remove Shader Progam
		void CShaderMaterial_OpenGL::RemoveShaderProgram()
		{
			if(CurrentlyBinded)
				Extensions->UseProgram(NULL);

			for(u8 i=0;i < JE_MAX_SHADER_GLOBAL_TECHNIQUES;i++)
				if(Pointers[i].SP)
				{
					Extensions->DetachShader(Pointers[i].SP,Pointers[i].VS);
					Extensions->DetachShader(Pointers[i].SP,Pointers[i].FS);
					Extensions->DeleteShader(Pointers[i].VS);
					Extensions->DeleteShader(Pointers[i].FS);
					Extensions->DeleteProgram(Pointers[i].SP);
				}
		}


	}
}

#endif
